package utils

/**
  @auth Crastlin@163.com
  @date 2020-10
*/

import (
	"EinoService/utils/upload"
	"fmt"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
)

type UploadFactory struct {
	driver  string
	config  *upload.Option
	errText string
	urls    []string
}

type FactoryUploader interface {
	SaveMultipart(form *multipart.Form, hashCheck string) bool
	SetAllowExtList(extList []string) FactoryUploader
	SetAllowMines(mineList []string) FactoryUploader
	SetMaxSize(maxSize int64) FactoryUploader
	Save(file *multipart.FileHeader, hashCheck string) bool
	UploadByRemoteFile(filename string) bool
	GetError() string
	GetUrl() string
	GetUrls() []string
}

const LOCAL = "local"
const OSS = "oss"

// Upload get upload client
func Upload(driver string, opt *upload.Option) FactoryUploader {
	// init base save path
	basePath := Configuration.Upload.Path
	if opt.RootPath != "" {
		opt.RootPath = strings.TrimSuffix(opt.RootPath, "/") + "/"
	}
	opt.RootPath = strings.TrimSuffix(basePath, "/") + "/" + opt.RootPath
	// init max size limit
	if opt.MaxSize <= 0 {
		opt.MaxSize = Configuration.Upload.MaxSize
	}
	// init allow ext limit
	if len(opt.AllowExtList) == 0 {
		opt.AllowExtList = Configuration.Upload.AllowExt
	}
	client := &UploadFactory{driver: driver, config: opt}
	return client
}

func (u *UploadFactory) SetAllowExtList(extList []string) FactoryUploader {
	u.config.AllowExtList = extList
	return u
}
func (u *UploadFactory) SetAllowMines(mineList []string) FactoryUploader {
	u.config.Mines = mineList
	return u
}
func (u *UploadFactory) SetMaxSize(maxSize int64) FactoryUploader {
	u.config.MaxSize = maxSize
	return u
}

// GetError get upload error
func (u *UploadFactory) GetError() string {
	return u.errText
}

// GetUrls get success url list
func (u *UploadFactory) GetUrls() []string {
	return u.urls
}

// GetUrl get success url
func (u *UploadFactory) GetUrl() string {
	if len(u.urls) > 0 {
		return u.urls[0]
	}
	return ""
}

// SaveMultipart upload multipart files
func (u *UploadFactory) SaveMultipart(form *multipart.Form, hashCheck string) bool {
	for _, files := range form.File {
		for _, file := range files {
			result := u.Save(file, hashCheck)
			if !result {
				return false
			}
		}
	}
	return true
}

// Save upload file
func (u *UploadFactory) Save(file *multipart.FileHeader, hashCheck string) bool {
	if file == nil {
		u.errText = "没有上传的图片"
		return false
	}

	// check upload driver been supported
	if !InMap(strings.ToLower(u.driver), []string{LOCAL, OSS}) {
		u.errText = fmt.Sprintf("driver %s is not defined", u.driver)
		return false
	}

	// check allow size
	if file.Size > u.config.MaxSize {
		u.errText = fmt.Sprintf("文件超出允许上传的大小，限制：%dM", u.config.MaxSize/1024/1024)
		return false
	}

	src, err := file.Open()

	if err != nil {
		u.errText = err.Error()
		return false
	}

	defer src.Close()

	buffer := make([]byte, 512)

	_, err = src.Read(buffer)
	if err != nil {
		panic(err)
	}

	u.config.RealContentType = http.DetectContentType(buffer)
	if u.config.RealContentType != "" && strings.Contains(u.config.RealContentType, "/") {
		es := strings.Split(u.config.RealContentType, "/")
		u.config.RealExt = Ternary(len(es) > 1, es[1], "")
	}
	if u.config.RealExt == "" {
		u.errText = "获取文件类型失败"
		return false
	}
	//check allow mines
	if len(u.config.Mines) > 0 {
		isAllowMine := false
		for _, allowMine := range u.config.Mines {
			if allowMine == u.config.RealContentType {
				isAllowMine = true
				break
			}
		}
		if !isAllowMine {
			u.errText = fmt.Sprintf("文件类型限制上传，请上传: %s", strings.Join(u.config.Mines, "，"))
			return false
		}
	}

	if len(u.config.AllowExtList) > 0 {
		isAllowExt := false
		for _, ext := range u.config.AllowExtList {
			if u.config.RealExt == ext {
				isAllowExt = true
				break
			}
		}
		if !isAllowExt {
			u.errText = fmt.Sprintf("文件类型限制上传，请上传: %s", strings.Join(u.config.AllowExtList, "，"))
			return false
		}
	}

	hashSrc, err := file.Open()

	if err != nil {
		u.errText = err.Error()
		return false
	}

	defer hashSrc.Close()

	// get hash value of file
	u.config.HashValue, err = GetFileHash(hashSrc, "md5")
	if err != nil {
		u.errText = err.Error()
		return false
	}

	if Configuration.App.Mode != "debug" && hashCheck != "" && hashCheck != u.config.HashValue {
		u.errText = "文件校验不正确"
		return false
	}

	var driver upload.Uploader
	switch strings.ToLower(u.driver) {
	case LOCAL:
		driver = upload.InitLocal(u.config, Configuration.App.Url)
		break
	//case FTP:
	//	driver = upload.InitFtp(u.config)
	//	break
	case OSS:
		aliYunConfig := Configuration.AliYun
		url := "https://" + aliYunConfig.Oss.Bucket + "." + strings.TrimPrefix(aliYunConfig.Oss.Endpoint, "https://")
		driver = upload.InitAliYun(&upload.AliYunOption{
			Key:      aliYunConfig.AccessKeyId,
			Secret:   aliYunConfig.AccessSecret,
			Bucket:   aliYunConfig.Oss.Bucket,
			Endpoint: aliYunConfig.Oss.Endpoint,
			Url:      url,
		})
		year, month, day := GetCurrentDate()
		driver.SetOption(&upload.Option{
			SavePath:  fmt.Sprintf("agreements/merchant/%d%d/%d", year, month, day),
			AutoSub:   false,
			HashValue: fmt.Sprintf("%s.%s", u.config.HashValue, u.config.RealExt),
		})
		break
	}

	// upload file
	src, err = file.Open()
	if err != nil {
		u.errText = err.Error()
		return false
	}
	defer src.Close()
	if !driver.Save(src) {
		u.errText = driver.GetError()
		return false
	}
	// append result url into urls
	u.urls = append(u.urls, driver.GetUrl())
	return true
}

func (u *UploadFactory) UploadByRemoteFile(filename string) bool {

	download := NewDownload().SetChunkSize(300 * 1024)
	download.SetAllowExt(u.config.AllowExtList)
	download.SetMaxSize(u.config.MaxSize)
	if !download.SaveBySlice(filename) {
		u.errText = download.GetError()
		return false
	}
	savedFile := download.GetFullPath()

	if savedFile == "" {
		u.errText = "获取缓存文件失败"
		return false
	}

	if strings.ToLower(u.driver) == LOCAL {
		u.urls = append(u.urls, savedFile)
		return true
	}

	f, err := os.Open(savedFile)
	if err != nil {
		u.errText = fmt.Sprintf("打开已缓存文件失败, %s", err.Error())
		return false
	}
	defer func() {
		f.Close()
		// 回收缓存文件
		err = os.Remove(savedFile)
		if err != nil {
			fmt.Println("delete temp file was failed")
		}
	}()

	newFilename := download.GetUniqueFilename()
	var driver upload.Uploader
	switch strings.ToLower(u.driver) {
	case OSS:
		aliYunConfig := Configuration.AliYun
		url := "https://" + aliYunConfig.Oss.Bucket + "." + strings.TrimPrefix(aliYunConfig.Oss.Endpoint, "https://")
		driver = upload.InitAliYun(&upload.AliYunOption{
			Key:      aliYunConfig.AccessKeyId,
			Secret:   aliYunConfig.AccessSecret,
			Bucket:   aliYunConfig.Oss.Bucket,
			Endpoint: aliYunConfig.Oss.Endpoint,
			Url:      url,
		})
		break
	}
	basePath := download.GetSubPath()
	if u.config.SavePath != "" {
		basePath = strings.TrimSuffix(u.config.SavePath, "/") + "/" + basePath
	}
	driver.SetOption(&upload.Option{
		SavePath:  basePath,
		AutoSub:   false,
		HashValue: newFilename,
	})
	if !driver.Save(f) {
		u.errText = driver.GetError()
		return false
	}
	u.urls = append(u.urls, driver.GetUrl())
	return true
}
